package donkeykong;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JPanel;

public class Barril extends JPanel implements Runnable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	/** Velocidad de bajar escaleras **/
	private int VELOCIDAD_BAJADA = 8;
	/** MOVIMIENTO DEL BARRIL, UNIDAD>0 CUANDO VA HACIA LA DERECHA, UNIDAD<0 CUANDO VA HACIA LA IZQUIERDA **/
	private int UNIDAD = 1;
	/** Velocidad de movimiento del arril **/
	private int VELOCIDAD = 0;
	/** Posicion X del barril **/
	private int posicionX_barril;
	/** Posicion Y del barril **/
	private int posicionY_barril;
	/** Imagen del fondo, necesaria para comprobar los colores de los pixeles **/
	private BufferedImage image1;
	

	
	/** Variable necesaria para mostrar las animaciones de los sprites de barril **/
	private int sprite;
	/** Imagenes de los sprites del barril **/
	private Image barrilBajando,barril1,barril2,barril3,barril4,imagenbarril,imagen_destr_1,imagen_destr_2,
				imagen_destr_3,imagen_destr_4;;
	/** Variable que indica que este barril debe sacarse de la lista de barriles activos en la pantalla **/
	private boolean barril_muerto;
	/** Indica si el barril esta bajando una escalera **/
	private boolean bajandoEscalera;
	/** Variable que sirve para analizar una escalera solo una vez, para no tener errores con los colores de la escalera **/
	private boolean yacomprobado;
	/** Posicion actual indica que valor se ha de tener en cuenta para las escaleras, si la posicion X original o la esquina, depende
	 * de si esta en direccion derecha (mirar posicion X) o direccion izquierda (mirar esquina)
	 */
	private int posicionactual;
	/** Esquina contraria a la posicion X del barril, para controlar las bajadas por la escalera **/
	private int esquina;
	/** Referencia a Mario, para averiguar si choca con el, o ya esta muerto **/
	private Mario mario;
	/** Variable que indica si el barril es un barril normal (0) o es un barril especial (1) **/
	private boolean barril_especial;
	
	private ArrayList<Espectro> espectros;
	public boolean ponerPuntuacion;

	private Puntuacion_imagen puntuacion;
	
	private boolean destruccion;
	
	private Puntuacion_total score;
	
	Thread p;
	private Clip clickClip;
	private AudioInputStream musicaSonido;
	private int NVdificultad;
	public Barril(int posx, int posy, BufferedImage imageFondo, int velocidadBarril, Mario m, boolean tipo, ArrayList<Espectro> esp,Puntuacion_total pt, int nivel_dificultad){
		try{
			
			espectros = esp;
			NVdificultad = nivel_dificultad;
			score = pt;
			
			barril_especial = tipo;
			mario = m;
			
			puntuacion = new Puntuacion_imagen(mario, 1);
			p = new Thread(puntuacion);
			bajandoEscalera = false;
			barril_muerto=false;
			setPosicionX(posx);
			setPosicionY(posy);
			image1 = imageFondo;
			VELOCIDAD = velocidadBarril;
			barrilBajando = ImageIO.read(new File("./images/tonel/tonel_1.png"));
			barril1 = ImageIO.read(new File("./images/tonel/tonel_2.png"));
			barril2 = ImageIO.read(new File("./images/tonel/tonel_3.png"));
			barril3 = ImageIO.read(new File("./images/tonel/tonel_4.png"));
			barril4 = ImageIO.read(new File("./images/tonel/tonel_5.png"));
			imagen_destr_1 = ImageIO.read(new File("./images/destruccion/destruccion_1.png"));
			imagen_destr_2 = ImageIO.read(new File("./images/destruccion/destruccion_2.png"));
			imagen_destr_3 = ImageIO.read(new File("./images/destruccion/destruccion_3.png"));
			imagen_destr_4 = ImageIO.read(new File("./images/destruccion/destruccion_4.png"));
			imagenbarril = barril1;
		}catch (Exception e){
			e.printStackTrace();
		}
		
	}
	
	
	/** Funcion paintComponent que es propia de los JPanels, que sirve para dibujar en el mismo la imagen que se quiera, entre otras opciones **/
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2d = (Graphics2D) g;
		/** Para hacer operaciones complejas con una imagen, crear un AffineTransform **/
		AffineTransform at = new AffineTransform();
		at.translate(getPosicionX(), getPosicionY() - imagenbarril.getHeight(null)); 
		if (isDestruido()){
			if (!ponerPuntuacion){
				animacionDestruccion(imagenbarril,at,g2d);
			}else{
				g2d.drawImage(imagenbarril,at,null);
			}
		}else{
		
		
		/** Si UNIDAD=0, significa que mario ha muerto/Se ha pasado la pantalla, y los barriles han de quedarse en la ultima posicion
		 *  y con el ultimo sprite valido **/
		if (UNIDAD!=0){
			/** Si esta bajando una escalera, poner sprite de la escalera **/
			if (bajandoEscalera){
				imagenbarril = barrilBajando;
			}else{
				/** Sino, ir alternando, dependiendo del sentido de la marcha del barril **/
				imagenbarril = ((sprite==0)? barril1:((sprite==1)? barril2:((sprite==2)? barril3: barril4)));
				if (UNIDAD > 0){
					sprite=(sprite+1)%4;
				}else{
					sprite=(sprite-1)%4;
					if(sprite<0)
						sprite=3;
			
				}
			}
		}


		g2d.drawImage(imagenbarril,at,null);
		

		}
		if (ponerPuntuacion){
			if (puntuacion!=null)
				puntuacion.paintComponent(g);
			
		}
	}
	
	/** Funcion para modificar la posicion X del barril **/
	private void setPosicionX(int posx) {
		posicionX_barril = posx;
	}

	/** Funcion para modificar la posicion Y del barril **/
	private void setPosicionY(int posy) {
		posicionY_barril = posy;
	}


	/** Animacion que se muestra cuando se destruye el barril **/
	private void animacionDestruccion(Image imagen,	AffineTransform at, Graphics2D g2d) {
		Image image;
		image = ((sprite<2)? imagen_destr_1 : ((sprite<4)? imagen_destr_2 : ((sprite<6)? imagen_destr_3 :imagen_destr_4))); 	
		sprite=(sprite+1);
		/**Borrar barril**/
		if (sprite==8){
			if (!isMuerto()){
				score.añadirPuntuacion(300);
				escucharSonido("item_points",0);
				try {
					imagenbarril = ImageIO.read(new File("./images/300_puntos.png"));
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				ponerPuntuacion=true;
				puntuacion=null;

			}
			
			
		}
		g2d.drawImage(image,at,null);
		
	}
	
	
	/** Runnable de la clase, se encarga de ir avanzando e ir mirando si encuentra escaleras o no **/
	@Override
	public void run() {
		try{

			if (!isMuerto()){
				if (!barril_especial){
					/** Comprobar el suelo, para hacer que el barril salga directamente del suelo.**/
					comprobarSuelo(getPosicionX()+imagenbarril.getWidth(null)/2);
					/** Asignar como esquina la posicion contraria a la posicion X **/
					setEsquina(getPosicionX()+imagenbarril.getWidth(null));
					/** Mientras no llegue a la parte inferior izquierda de la pantalla **/
					while ((getPosicionX() > 30) | (getPosicionY() < 598)){
						    /** Si se destruye por el mazo, esperar un pequeño tiempo para mostrar la puntuacion y destruirlo  **/
							if (mario.isInvencible() & ponerPuntuacion){
								Thread.sleep(1000);
								destruirBarril();
							}
							/**  Si barril destruido, dejar movimiento **/
							if (isMuerto()){
								break;
							/** Si esta siendo destruido, no hacer nada **/
							}else if (isDestruido()){
								
							}else{
								/** Sleep para que se haga fluido el movimiento y no inmediato **/
								Thread.sleep(102);
								if (ponerPuntuacion){
										if (puntuacion.Acabar()){
											score.añadirPuntuacion(100);
											puntuacion.setAcabar(false);
											p = new Thread(puntuacion);
											ponerPuntuacion = false;
										}
									
								}
								/** Pixel a pixel, vamos moviendo el barril **/
								for (int i=1;i<=VELOCIDAD;i++){
									/** Modificamos la posicion X  y la esquina segun el valor de UNIDAD, si esta dirigiendose a la derecha es UNIDAD=1,
									 *  si es a la izquierda es UNIDAD = -1
									 */
									setPosicionX(getPosicionX()+UNIDAD);
									setEsquina(getEsquina()+UNIDAD);
									
									/** Comprobar choque con Mario **/
									comprobarChoque();
									
									/** Comprobar puntuacion si salta y si no esta muerto **/
									AñadirPuntuacionSalto();
									
									/** Poner la posicionactual a mirar como la que depende de la direccion del barril **/
									posicionactual = (UNIDAD>0 ? getPosicionX(): getEsquina());
									
									/** Comprobar suelo en esa posicion **/
									comprobarSuelo(posicionactual);
			
									/** Si encuentra una escalera **/
									if (comprobarEscalera(getPosicionY()+2)){
										/** Si ya se ha comprobado esa escalera, no volver a mirarla. **/
										if (!isYacomprobado()){
											setYacomprobado(true);
											Random rand = new Random();
											// 0: baja, 1,2: no baja
											int accion_barril = rand.nextInt(3);  
											/** Elige si bajar por la escalera, segun un valor aleatorio **/
											if (accion_barril==0){
													setPosicionY(getPosicionY()+2);
													bajandoEscalera = true;
													bajarEscalera();
											}
										}
									/** Si no detecta escalera, y ya habia comprobado una, debemos decir que ya esa escalera la hemos dejado atras
									 * y podemos comprobar otra
									 */
									}else{
										if (isYacomprobado()){
											setYacomprobado(false);
										}
									}
									
									
								}
							}
						}
							
					/** Es un barril de generar una llama, por lo que debe bajar siempre hacia abajo **/
				}else{
					VELOCIDAD_BAJADA = 12;
					/** Bajar en vertical **/
					while (getPosicionY() < 580){
						/** Poner la posicionactual a mirar como la que depende de la direccion del barril **/
						posicionactual = getPosicionX();
						bajarEscalera();
						bajandoEscalera = true;
						setPosicionY(getPosicionY()+2);
						
						/** Comprobar suelo en esa posicion **/
						comprobarSuelo(posicionactual);
					}
					setPosicionY(getPosicionY()-2);
	
					/** Comprobar suelo en esa posicion **/
					comprobarSuelo(posicionactual);
					bajandoEscalera = false;
					/** Dirigirse al barril de aceite **/
					while(getPosicionX() > 60){
						if (UNIDAD!=0){
							Thread.sleep(102);
							for (int i=1;i<=VELOCIDAD;i++){
								if (!mario.isMuriendo()){
									setPosicionX(getPosicionX()-1);
									setEsquina(getEsquina()-1);
									/** Comprobar choque con Mario **/
									comprobarChoque();
									/** Poner la posicionactual a mirar como la que depende de la direccion del barril **/
									posicionactual = (UNIDAD>0 ? getPosicionX(): getEsquina());
									/** Comprobar suelo en esa posicion **/				
									comprobarSuelo(posicionactual);
								}
							}
						}
					}
					/** Generar una llama **/
					int velocidad = (NVdificultad==1?4:(NVdificultad<4?5:7));
					Espectro llama = new Espectro(60, 598, true, mario, image1,velocidad,velocidad,1, score);  //TODO  esto cambiarlo segun dificultad (4,4 LVL1, 5,5 LVL2,3,  7,7 LVL4+)
					espectros.add(llama);
					Thread espectro = new Thread(llama);
					espectro.setName("espectro");
					espectro.start();
					
					
				
				}
			}
		/** Cuando llega al destino, destruir barril **/
			if (!isMuerto()){
				destruirBarril();
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	/** Funcion que comprueba si el centro del barril coincide con el centro de Mario, y que éste este en una posicion Y inferior ( este por encima ) **/
	private void AñadirPuntuacionSalto() {
		/** Punto central del barril **/
		int posicion_centro_x = getPosicionX()+ imagenbarril.getWidth(null)/2 ;
		int posicion_centro_y = getPosicionY()- imagenbarril.getHeight(null)/2 ;
		
		/** Punto central de Mario **/
		int mario_centro_x = mario.getPosicion_x()+mario.getImage().getWidth(null)/2;
		int mario_centro_y = mario.getPosicion_y()-mario.getImage().getHeight(null)/2;
		
		/** Solo sumar puntuacion si Mario esta saltando, el barril pasa por el centro X de Mario, y la diferencia de altura es la de un salto **/
		if (mario.isSaltando() & posicion_centro_x==mario_centro_x & Math.abs(posicion_centro_y-mario_centro_y)<45){
			if (!ponerPuntuacion){
				if (!p.isAlive()){
					try{
					p.setName("PUNTUACIONSALTO");
					p.start();
					ponerPuntuacion=true;
	
					}catch(IllegalThreadStateException e){
					}
				}
			}
		}

	}


	/** Funcion que sirve para comprobar si el barril choca con Mario **/
	private void comprobarChoque() {
		/** Punto central del barril **/
		int posicion_centro_x = getPosicionX()+ imagenbarril.getWidth(null)/2 ;
		int posicion_centro_y = getPosicionY()- imagenbarril.getHeight(null)/2 ;
		
		/** Esquinas de la imagen de Mario **/
		int mario_x_final = mario.getPosicion_x()+mario.getImage().getWidth(null);
		int mario_x_inicial = mario.getPosicion_x();
		int mario_y_final = mario.getPosicion_y();
		int mario_y_inicial = mario.getPosicion_y()-mario.getImage().getHeight(null);
		
		
		/** Si el centro del baril choca con la imagen de Mario, éste muere **/
		if (posicion_centro_x <= mario_x_final & posicion_centro_x >= mario_x_inicial){
			if (posicion_centro_y <= mario_y_final & posicion_centro_y >= mario_y_inicial){
				if (!mario.isInvencible())
					mario.Muriendo();
				else
					if ((mario.MirandoDcha() & UNIDAD==1 & mario.getPosicion_x()==getPosicionX()+imagenbarril.getWidth(null))|(!mario.MirandoDcha() & UNIDAD==-1 & mario.getPosicion_x()+mario.getImage().getWidth(null)==getPosicionX())){
						mario.Muriendo();
					}else{
					Destruccion();
					}
			}
		}

		
	}

	/** Barril muere por el mazo **/
	private void Destruccion() {
		escucharSonido("destruido",0);
		/** Animacion destruccion **/
		setDestruido(true);
		
	}

	public boolean isDestruido() {
		return destruccion;
	}


	private void setDestruido(boolean destruccion) {
		this.destruccion = destruccion;
	}
	
	/**
	 * Funcion que devuelve el valor de la esquina del barril 
	 * @return el valor de la esquina del barril 
	 */
	private int getEsquina() {
		return esquina;
	}

	/**
	 * Funcion que modifica el valor de la esquina del barril 
	 * @param i : nuevo valor de la esquina del barril
	 */
	private void setEsquina(int i) {
		esquina = i;		
	}


	/**
	 * Funcion que realiza el descenso por una escalera
	 */
	private void bajarEscalera() {
		try{
			/** Obtener color pixel a pixel, y bajar hasta que no se encuentra suelo **/
			int c1 = image1.getRGB(posicionactual,getPosicionY());
			Color color = new Color(c1);
			while (!EsSuelo(color)){
				Thread.sleep(102);
				for (int i=1;i<=VELOCIDAD_BAJADA;i++){
					if (UNIDAD!=0){
						setPosicionY(getPosicionY()+1);
						c1 = image1.getRGB(posicionactual,getPosicionY());
						color = new Color(c1);
						if (EsSuelo(color)){
							i = VELOCIDAD_BAJADA;
						}
						/** En cada pixel que baja comprobar que choca con Mario **/
						comprobarChoque();
					}else{
						break;
					}
				}
			}
			bajandoEscalera = false;
			UNIDAD = -UNIDAD;
		}catch (Exception e){
			e.printStackTrace();
		}
	}

	/** Funcion que comprueba el color de un pixel, y dice si es el de una escalera **/
	private boolean comprobarEscalera(int posicionY) {
		int c1 = image1.getRGB(posicionactual,posicionY);
		Color color = new Color(c1);
		return EsEscalera(color);
	}

	/** Funcion que marca el barril para ser destruido **/
	public void destruirBarril() {
		barril_muerto = true;
	}

	/** 
	 * Devuelve el valor de la posicion Y del barril 
	 * @return valor de la posicion Y del barril 
	 */
	private int getPosicionY() {
		return posicionY_barril;
	}
	
	/** 
	 * Devuelve el valor de la posicion X del barril 
	 * @return valor de la posicion X del barril 
	 */
	private int getPosicionX() {
		return posicionX_barril;
	}
	
	/** Funcion que modifica el valor de UNIDAD a 0 para indicar que Mario ha muerto o se ha acabado la pantalla **/
	public void Acabar(){
		UNIDAD = 0;
	}
	
	
	
	/** Funcion de comprobar suelo, sirve para poder moverse por la pantalla, poder caer, subir/bajar escaleras,etc **/
	public void comprobarSuelo(int centroBarril){
		try{
			int c1 = image1.getRGB(centroBarril,getPosicionY());
			Color color = new Color(c1);
			/** Comprobar el suelo si es negro, (color del aire) , es bajar posicion 'y' hasta encontrar color de suelo **/
			if ((EsAire(color))){
				c1 = image1.getRGB(centroBarril,getPosicionY());
				color = new Color(c1);
				int caida=0;
				/** Mientras el color no sea el del suelo, bajar **/
				while (!(EsSuelo(color))){
					if(UNIDAD!=0){
						 setPosicionY(getPosicionY()+1);
						 Thread.sleep(10);
						 c1 = image1.getRGB(centroBarril,getPosicionY());
						 color = new Color(c1);
						 caida++;
						 comprobarChoque();
					}else{
						break;
					}
				}
				/** Si la caida es muy grande, cambio de sentido **/
				if (caida>20){
					UNIDAD = -UNIDAD;
				}			
				
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	/** Funcion que dado un color, indica si es el color del suelo **/
	public boolean EsSuelo(Color color){
		return (color.getRed()>250 & color.getGreen()<50 & color.getBlue()>100);
	}
	
	/** Funcion que dado un color, indica si es el color del aire **/
	public boolean EsAire(Color color){
		return (color.getRed()==0 & color.getGreen()==0 & color.getBlue()==0);
	}
	
	/** Funcion que dado un color, indica si es el color de una escalera **/
	public boolean EsEscalera(Color color){
		return (color.getRed()<6 & color.getGreen()>200 & color.getBlue()>200);
	}

	/**
	 * Funcion que comprueba si el barril debe destruirse
	 * @return cierto si el barril debe ser destruido, falso en caso contrario
	 */
	public boolean isMuerto() {
		return barril_muerto;
	}

	/**
	 * Comprueba si una escalera ya ha sido comprobada, y por lo tanto los demas pixeles del mismo color de la misma escalera no son tratados
	 * @return cierto si una escalera ya ha sido tratada, falso en caso contrario
	 */
	private boolean isYacomprobado() {
		return yacomprobado;
	}

	/**
	 * Indica que una escalera ha de ponerse como comprobada o no, para cuando se detectan los pixeles de la misma escalera hacer una accion u otra
	 * @param comp valor  cierto si la escalera tiene que ser comprobada, falso si debe dejarse de estar comprobada
	 */
	private void setYacomprobado(boolean comp) {
		yacomprobado = comp;
	}	
	
	/** Funcion que permite reproducir un sonido
	 * 
	 * @param sonido : Nombre (sin extension) del sonido a reproducir
	 * @param duracion : Duracion de la musica por si qse quiere repetir indefinidamente o solo unas veces
	 */
	public void escucharSonido(String sonido, int duracion){
		try {
			clickClip = AudioSystem.getClip();
			musicaSonido = AudioSystem.getAudioInputStream(new File("./sounds/"+sonido+".wav"));
			clickClip.open(musicaSonido);
			clickClip.loop(duracion);
			clickClip.start();
		} catch (LineUnavailableException e) {
			e.printStackTrace();
		} catch (UnsupportedAudioFileException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	/** Funcion que se utiliza para parar el sonido, si esta activado **/
	public void pararSonido(){
		if(clickClip != null){
			clickClip.stop();
			clickClip.close();
		}
	}

}
